/*
 * left to be done:
 * doMove() --Tony
 * checkWinner() --William 
 * getValue() --William 
 */
package connectfour;

import java.util.Stack;

public class Board {
    /*
     * the board is a 2 dimentional array of integers that correspond to each
     * place on the board -1 = no piece is in that place 0 = player one is
     * occupying that place 1 = player two is occupying that place
     */

    private int board[][];
    private int numRows;
    private int numCols;
    private int connectN = 4;
    private Stack<Integer> rowStack = new Stack();
    private Stack<Integer> colStack = new Stack();
    private int[] colHeight;

    public int getNumRows() {
        return numRows;
    }

    public int getNumCols() {
        return numCols;
    }

    /**
     * this is a copy constructor which will make a copy of 'b'
     * @param b is the Board class to copy
     */
    public Board(Board b) {
        this.numRows = b.numRows;
        this.numCols = b.numCols;
        board = new int[numRows][numCols];
        colHeight = new int[numCols];
        for (int y = 0; y < numCols; y++) {
            colHeight[y] = b.colHeight[y];
            for (int x = 0; x < numRows; x++) {
                board[x][y] = b.board[x][y];
            }
        }
    }

    // changed this slightly, see gamecontroller for clarification
    public Board(int numRows, int numCols/*, GUI gui*/) {
        //this.gui = gui;
        board = new int[numRows][numCols];
        this.numRows = numRows;
        this.numCols = numCols;
        
        colHeight = new int[numCols];

        // initialize board to empty
        for (int y = 0; y < numCols; y++) {
            colHeight[y] = 0;
            for (int x = 0; x < numRows; x++) {
                board[x][y] = -1;
            }
        }
    }

    public Board(int numRows, int numCols, int n) {
        //this.gui = gui;
        board = new int[numRows][numCols];
        this.numRows = numRows;
        this.numCols = numCols;
        this.connectN = n;

        colHeight = new int[numCols];

        // initialize board to empty
        for (int y = 0; y < numCols; y++) {
            colHeight[y] = 0;
            for (int x = 0; x < numRows; x++) {
                board[x][y] = -1;
            }
        }
    }

    public int[][] getBoard() {
        return board;
    }

    public int getPiece(int x, int y) {
        return board[x][y];
    }

    /**
     * update the game board with the new move
     *
     * @param player
     *            is the player id making the move
     * @param col
     *            is the column id (0 to sizeX)
     * @return true if the move is valid, false otherwise
     */
    public boolean doMove(int player, int col) {
        // if col = -1 then it was an invalid move return false - only applies for human players i think
        if (col == -1) {
            return false;
        }
        
        if (colHeight[col] < numRows) {
            board[numRows - 1 - colHeight[col]][col] = player;
            rowStack.push(numRows - 1 - colHeight[col]);
            colStack.push(col);
            colHeight[col]++;
            return true;
        }
        
        /*
        // check in what row the highest piece in that column is in
        for (int k = this.numRows - 1; k >= 0; k--) {
            if (this.board[k][col] == -1) {
                this.board[k][col] = player;
                xStack.push(k);
                yStack.push(col);
                //System.out.println("PUSH: " + xStack.peek() + " | " + yStack.peek());
                return true;
            }
        }
        */
        return false;
    }

    public void undoMove() {
        //if (!xStack.empty())
        //System.out.println("POP: " + xStack.peek() + " | " + yStack.peek());    
        int row = rowStack.pop();
        int col = colStack.pop();
        board[row][col] = -1;
        colHeight[col]--;
    }

    /**
     * check if there is a winner
     * @return the id of the winner, -1 means no winner, 2 means tie
     */
    public int checkWinner(boolean a) {
        int winner = -1;
        if (isEmpty()) {
            return -1;
        }

        for (int i = 0; i < numRows; i++) {
            for (int j = 0; j < numCols - (connectN - 1); j++) {
                winner = checkHorizontal(i, j);
                if (winner >= 0) {
                    return winner;
                }
            }
        }
        for (int i = 0; i < numRows - (connectN - 1); i++) {
            for (int j = 0; j < numCols; j++) {
                winner = checkVertical(i, j);
                if (winner >= 0) {
                    return winner;
                }
            }
        }
        for (int i = (connectN - 1); i < numRows; i++) {
            for (int j = 0; j < numCols - (connectN - 1); j++) {
                winner = checkUpDiagonal(i, j);
                if (winner >= 0) {
                    return winner;
                }
            }
        }
        for (int i = 0; i < numRows - (connectN - 1); i++) {
            for (int j = 0; j < numCols - (connectN - 1); j++) {
                winner = checkDownDiagonal(i, j);
                if (winner >= 0) {
                    return winner;
                }
            }
        }
        if (isFull()) {
            return 2;
        }
        return winner;
    }
    public int checkWinner() {
        //if (isEmpty()) {
          //  return -1;
        //}
        //check for vertical winners
        for (int col = 0; col < numCols; col++) {
            if (colHeight[col] >= connectN) {
                int row = numRows - 2;
                int inARow = 1;
                int player = board[numRows - 1][col];
                while (row > numRows - 1 - colHeight[col] && inARow < connectN) {
                    if (board[row][col] != player) {
                        inARow = 0;
                        player = board[row][col];
                    }
                    row--;
                    inARow++;
                }
                if (inARow >= connectN) {
                    return player;
                }
            }
        }
        //check for horizontal winners
        for (int row = 0; row < numRows; row++) {
            int col = 1;
            int inARow = 1;
            int player = board[row][0];
            while (col < numCols && inARow < connectN) {
                if (board[row][col] != player) {
                    inARow = 0;
                    player = board[row][col];
                }
                col++;
                if (player != -1) {
                    inARow++;
                }
            }
            if (inARow >= connectN) {
                return player;
            }
        }
        //check for up diagnoal
        for (int row = connectN - 1; row < numRows; row++) {
            int inARow = 1;
            int player = board[row][0];
            int count = 1;
            while ((row - count) >= 0 && inARow < connectN) {
                int r = row - count;
                int c = count;
                if (board[r][c] != player) {
                    inARow = 0;
                    player = board[r][c];
                }
                count++;
                if (player != -1) {
                    inARow++;
                }
            }
            if (inARow >= connectN) {
                return player;
            }
        }
        for (int col = 0; col <= numCols - connectN; col++) {
            int inARow = 1;
            int player = board[numRows - 1][col];
            int count = 1;
            while (count < numRows && (col + count) < numCols && inARow < connectN) {
                int r = numRows - count - 1;
                int c = col + count;
                if (board[r][c] != player) {
                    inARow = 0;
                    player = board[r][c];
                }
                count++;
                if (player != -1) {
                    inARow++;
                }
            }
            if (inARow >= connectN) {
                return player;
            }
        }
        //check for down diagnoal
        for (int row = 0; row <= numRows - connectN; row++) {
            int inARow = 1;
            int player = board[row][0];
            int count = 1;
            while ((row + count) < numRows && inARow < connectN) {
                int r = row + count;
                int c = count;
                if (board[r][c] != player) {
                    inARow = 0;
                    player = board[r][c];
                }
                count++;
                if (player != -1) {
                    inARow++;
                }
            }
            if (inARow >= connectN) {
                return player;
            }
        }
        for (int col = 0; col <= numCols - connectN; col++) {
            int inARow = 1;
            int player = board[0][col];
            int count = 1;
            while (count < numRows && (col + count) < numCols && inARow < connectN) {
                int r = count;
                int c = col + count;
                if (board[r][c] != player) {
                    inARow = 0;
                    player = board[r][c];
                }
                count++;
                if (player != -1) {
                    inARow++;
                }
            }
            if (inARow >= connectN) {
                return player;
            }
        }
        if (isFull()) {
            return 2;
        }
        return -1;
    }

    /**
     * Check if board is full
     *
     * @return true if full
     */
    public boolean isFull() {
        boolean full = true;
        for (int i = 0; i < numCols; i++) {
            if (board[0][i] == -1) {
                return false;
            }
        }
        return full;
    }

    public boolean isEmpty() {
        for (int i = 0; i < numCols; i++) {
            if (getColumnHeight(i) > 0) {
                return false;
            }
        }
        return true;
    }

    /**
     * Given a board position, check for winner in the same row, ie. 4 in a row
     *
     * @param row
     * @param column
     * @return winner id.
     */
    private int checkHorizontal(int row, int column) {

        int player = board[row][column];
        int count = 0;
        int winner = -1;

        if (player == -1) {
            return winner;
        }
        if (column > numCols - connectN) {
            return winner;
        } else {
            for (int i = 1; i <= (connectN - 1); i++) {
                if (board[row][column + i] == player) {
                    count++;
                    if (count == (connectN - 1)) {
                        winner = player;
                        return winner;
                    }
                }
            }
        }
        return winner;
    }

    /**
     * Given a board position, check for winner in the same column, ie. 4 in a
     * column
     *
     * @param row
     * @param column
     * @return winner id.
     */
    private int checkVertical(int row, int column) {

        int player = board[row][column];
        int count = 0;
        int winner = -1;

        if (player == -1) {
            return winner;
        }
        if (row > numRows - connectN) {
            return winner;
        } else {
            for (int i = 1; i <= (connectN - 1); i++) {
                if (board[row + i][column] == player) {
                    count++;
                    if (count == (connectN - 1)) {
                        winner = player;
                        return winner;
                    }
                }
            }
        }
        return winner;
    }

    /**
     * Check up diagonal "ie. positive slope" for winner at given position
     *
     * @param row
     * @param column
     * @return winner id.
     */
    private int checkUpDiagonal(int row, int column) {

        int player = board[row][column];
        int count = 0;
        int winner = -1;

        if (player == -1) {
            return winner;
        } else if (row < (connectN - 1)) {
            return winner;
        } else if (column > numCols - connectN) {
            return winner;
        } else {
            for (int i = 1; i <= (connectN - 1); i++) {
                if (board[row - i][column + i] == player) {
                    count++;
                    if (count == (connectN - 1)) {
                        winner = player;
                        return winner;
                    }
                }
            }
        }
        return winner;
    }

    /**
     * Check down diagonal "ie. negative slope" for winner at given position
     *
     * @param row
     * @param column
     * @return winner id.
     */
    private int checkDownDiagonal(int row, int column) {

        int player = board[row][column];
        int count = 0;
        int winner = -1;

        if (row > numRows - connectN) {
            return winner;
        }
        if (column > numCols - connectN) {
            return winner;
        } else {
            for (int i = 1; i <= (connectN - 1); i++) { // for addition purpose
                if (board[row + i][column + i] == player) {
                    count++;
                    if (count == (connectN - 1)) {
                        winner = player;
                        return winner;
                    }
                }
            }
        }
        return winner;
    }

    /**
     * get the value of the current board
     * @return the booard value (0.0 to 1.0)
     */
    public double getValue() {

        int horizontal = 0;
        int vertical = 1;
        int upDiagonal = 2;
        int downDiagonal = 3;

        int val0[];
        int val1[];

        val0 = new int[4];
        val1 = new int[4];

        for (int x = 0; x < 4; x++) {
            val0[x] = 1000;
            val1[x] = 1000;
        }

        if (isEmpty()) {
            return 0.5;
        }
        int tmp;
        for (int i = 0; i < numRows; i++) {//for int i=0;i<numRows;i++
            for (int j = 0; j < numCols - (connectN - 1); j++) {//for int j=0;j<numCols - 3;j++
                tmp = getHorizontalSteps(0, i, j);
                if (tmp < val0[horizontal]) {
                    val0[horizontal] = tmp;
                }
                tmp = getHorizontalSteps(1, i, j);
                if (tmp < val1[horizontal]) {
                    val1[horizontal] = tmp;
                }
            }
        }

        for (int i = 0; i < numRows - (connectN - 1); i++) {
            for (int j = 0; j < numCols; j++) {
                tmp = getVerticalSteps(0, i, j);
                if (tmp < val0[vertical]) {
                    val0[vertical] = tmp;
                }
                tmp = getVerticalSteps(1, i, j);
                if (tmp < val1[vertical]) {
                    val1[vertical] = tmp;
                }
            }
        }

        for (int i = (connectN - 1); i < numRows; i++) {
            for (int j = 0; j < numCols - (connectN - 1); j++) {
                tmp = getUpDiagonalSteps(0, i, j);
                if (tmp < val0[upDiagonal]) {
                    val0[upDiagonal] = tmp;
                }
                tmp = getUpDiagonalSteps(1, i, j);
                if (tmp < val1[upDiagonal]) {
                    val1[upDiagonal] = tmp;
                }
            }
        }

        for (int i = 0; i < numRows - (connectN); i++) {
            for (int j = 0; j < numCols - (connectN - 1); j++) {
                tmp = getDownDiagonalSteps(0, i, j);
                if (tmp < val0[downDiagonal]) {
                    val0[downDiagonal] = tmp;
                }
                tmp = getDownDiagonalSteps(1, i, j);
                if (tmp < val1[downDiagonal]) {
                    val1[downDiagonal] = tmp;
                }
            }
        }

        /*
        int minIndex0 = -1;
        int minIndex1 = -1;
        int sumOfSteps = -1;
        minIndex0 = findMinIndex(val0);
        minIndex1 = findMinIndex(val1);
        sumOfSteps = val0[minIndex0] + val1[minIndex1];
        value = (double) val0[minIndex0] / sumOfSteps;
         */

        int min0 = minOfArray(val0);
        int min1 = minOfArray(val1);
        if (min0 == 0 && min1 == 0) {
            //System.out.println("MIN0 & MIN1 = 0");
            return 0.5;
        }
        return ((double) min0) / (min0 + min1);
    }

    private int minOfArray(int[] a) {
        int min = Integer.MAX_VALUE;
        for (int i = 0; i < a.length; i++) {
            min = Math.min(min, a[i]);
        }
        return min;
    }

    /**
     * 
     * @param row
     * @param column
     * @return number of steps left to win in the givern pattern
     */
    private int getHorizontalSteps(int player, int row, int column) {

        int value = 100;
        int step = 0;

        for (int i = 0; i <= (connectN - 1); i++) {
            if (board[row][column + i] != player) {
                if (board[row][column + i] >= 0) {
                    return value;
                } else {
                    step += ((numRows - row) - getColumnHeight(column + i));
                }
            }
        }
        value = step;
        return value;
    }

    /**
     * 
     * @param row
     * @param column
     * @return number of steps left to win in the givern pattern
     */
    private int getVerticalSteps(int player, int row, int column) {

        int value = 100;
        int step = 0;
        int count = 0;

        for (int i = 0; i <= (connectN - 1); i++) {
            if (board[row + i][column] != player) {
                if (board[row + i][column] >= 0) {
                    return value;
                } else {
                    count++;
                    step = count;
                }
            }
        }
        value = step;
        return value;
    }

    /**
     * 
     * @param row
     * @param column
     * @return number of steps left to win in the givern pattern
     */
    private int getUpDiagonalSteps(int player, int row, int column) {
        int value = 100;
        int step = 0;

        for (int i = 0; i <= (connectN - 1); i++) {
            if (row < 3) {
                step = 100;
                break;
            } else if (column > numCols - (connectN)) {
                step = 100;
                break;
            } else {
                if (board[row - i][column + i] != player) {
                    if (board[row - i][column + i] >= 0) {
                        step = 100;
                        break;
                    } else {
                        step += ((numRows - (row - i)) - getColumnHeight(column + i));
                    }
                }
            }
        }
        value = step;
        return value;
    }

    /**
     * 
     * @param row
     * @param column
     * @return number of steps left to win in the givern pattern
     */
    private int getDownDiagonalSteps(int player, int row, int column) {
        int value = 100;
        int step = 0;

        if (row >= numRows - (connectN)) {
            return value;
        } else if (column >= numCols - (connectN)) {
            return value;
        } else {
            for (int i = 1; i <= (connectN - 1); i++) {
                if (board[row + i][column + i] != player) {
                    if (board[row + i][column + i] >= 0) {
                        step = 100;
                        break;
                    } else {
                        step += ((numRows - (row + i)) - getColumnHeight(column + i));
                    }
                }
            }
            value = step;
        }
        value = step;
        return value;
    }

    /**
     * return the column height of the given column
     * @param column
     * @return integer height of the column
     */
    private int getColumnHeight(int column) {
        int height = 0;
        for (int i = numRows - 1; i >=
                0; i--) {
            if (board[i][column] >= 0) {
                height++;
            } else {
                break;
            }
        }
        return height;
    }
}
